<!DOCTYPE html>
<html>

  <head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <title>安卓自定义View进阶-多点触控详解</title>
  <meta name="description" content="Android 多点触控详解，在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案，本次带大家了解 Android 多点触控相关的一些知识。">
  <meta name="author" content="GcsSloop">
  <meta name="keywords" content="Multitouch,Matrix,actionIndex,pointIndex,pointId,多点触控,多指触控,自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  <meta name="关键字" content="Multitouch,Matrix,actionIndex,pointIndex,pointId,多点触控,多指触控,自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  

  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="安卓自定义View进阶-多点触控详解">
  <meta name="twitter:description" content="Android 多点触控详解，在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案，本次带大家了解 Android 多点触控相关的一些知识。">
  <meta name="twitter:keywords" content="Multitouch,Matrix,actionIndex,pointIndex,pointId,多点触控,多指触控,自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  
  <meta property="og:type" content="article">
  <meta property="og:title" content="安卓自定义View进阶-多点触控详解">
  <meta property="og:description" content="Android 多点触控详解，在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案，本次带大家了解 Android 多点触控相关的一些知识。">
  <meta name="og:keywords" content="Multitouch,Matrix,actionIndex,pointIndex,pointId,多点触控,多指触控,自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">

  <meta name="theme-color" content="#343434">
  
  <link rel="icon" type="image/png" href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" />
  <link href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" rel="shortcut icon" type="image/png">
  
  <link href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
  <link rel="stylesheet" href="/css/main.css">

  <link rel="canonical" href="http://www.gcssloop.com/customview/multi-touch">
  <link rel="alternate" type="application/rss+xml" title="GcsSloop" href="http://www.gcssloop.com/feed.xml">
  
  <meta name="google-site-verification" content="Z_g58PkzRAyBMxkqrcDdWrTBK8oOWM-7rUHauhLNF2E" />
  <meta name="baidu-site-verification" content="kUtTXCKaZs" />
  <meta name="baidu-site-verification" content="6DuDv3aaJX" />
  
  <!--阅读次数统计-->
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"> </script>
  
  <!--Fuck Weixin and Baidu-->
  <meta http-equiv="Cache-Control" content="no-transform">
  <meta http-equiv=”Cache-Control” content=”no-siteapp” />
  <meta name="applicable-device" content="pc,mobile">
  <meta name="HandheldFriendly" content="true"/>

  <!-- Google Ad -->
  <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <script>
    (adsbygoogle = window.adsbygoogle || []).push({
      google_ad_client: "ca-pub-2767831356529421",
      enable_page_level_ads: true
    });
  </script>

</head>


  <body>

    <span class="mobile btn-mobile-menu">
        <i class="fa fa-list btn-mobile-menu__icon"></i>
        <i class="fa fa-angle-up btn-mobile-close__icon hidden"></i>
    </span>
    
    <header class="panel-cover panel-cover--collapsed" style="background-image: url('/assets/siteinfo/background-cover.jpg')">
  <div class="panel-main">

    <div class="panel-main__inner panel-inverted">
    <div class="panel-main__content">

        <a href="/#blog" title="前往 GcsSloop 的主页" class="blog-button"><img src="/assets/siteinfo/avatar.jpg" width="80" alt="GcsSloop logo" class="panel-cover__logo logo" /></a>
        <h1 class="panel-cover__title panel-title"><a href="/#blog" title="link to homepage for GcsSloop" class="blog-button">GcsSloop</a></h1>

        
        <span class="panel-cover__subtitle panel-subtitle">Just do IT later.</span>
        
        <hr class="panel-cover__divider" />
        <p class="panel-cover__description">嗨，我是 GcsSloop，一名来自2.5次元的魔法师，Android自定义View系列文章作者，非著名程序员。</p>
        <hr class="panel-cover__divider panel-cover__divider--secondary" />
        
        
        <p class="panel-cover__description">欢迎来到我的魔法世界!</p>
        
        
        <div class="navigation-wrapper">
          <div>
            <nav class="cover-navigation cover-navigation--primary">
              <ul class="navigation">
                <li class="navigation__item"><a href="/#blog" title="访问博客" class="blog-button">博客</a></li>
                
                  
                    <li class="navigation__item"><a href="https://github.com/GcsSloop" target="_blank" title="GcsSloop's GitHub">GitHub</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/timeline" title="博客目录">目录</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="https://xiaozhuanlan.com/u/GcsSloop" target="_blank" title="小专栏">专栏</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/category/customview" title="自定义View教程目录">自定义控件</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/friends" title="友链">友链</a></li>
                  
                  
                
              </ul>
            </nav>
          </div>
          
          <div><nav class="cover-navigation navigation--social">
  <ul class="navigation">

  
  <!-- Weibo -->
  <li class="navigation__item">
    <a href="http://weibo.com/GcsSloop" title="@GcsSloop 的微博" target="_blank">
      <i class='social fa fa-weibo'></i>
      <span class="label">Weibo</span>
    </a>
  </li>
  

  
  <!-- Github -->
  <li class="navigation__item">
    <a href="https://github.com/GcsSloop" title="@GcsSloop 的 Github" target="_blank">
      <i class='social fa fa-github'></i>
      <span class="label">Github</span>
    </a>
  </li>
  
  
  
  <!-- Twitter -->
  <li class="navigation__item">
    <a href="http://twitter.com/GcsSloop" title="@GcsSloop" target="_blank">
      <i class='social fa fa-twitter'></i>
      <span class="label">Twitter</span>
    </a>
  </li>
  

    

  

  
  <!-- RSS -->
  <li class="navigation__item">
    <a href="/feed.xml" rel="author" title="RSS" target="_blank">
      <i class='social fa fa-rss'></i>
      <span class="label">RSS</span>
    </a>
  </li>
  

  
  <!-- Email -->
  <li class="navigation__item">
    <a href="mailto:GcsSloop@gmail.com" title="发邮件给我">
      <i class='social fa fa-envelope'></i>
      <span class="label">Email</span>
    </a>
  </li>
  

  
  <!-- Copyright -->
  <li class="navigation__item">
    <a href="http://choosealicense.online" title="选择版权"  target="_blank">
      <i class="social fa fa-copyright"></i>
      <span class="label">版权</span>
    </a>
  </li>
  
  
  </ul>
</nav>
</div>
        </div>
      </div>
    </div>
    
    
    <div class="panel-cover--overlay cover-slate"></div>
    
  </div>
</header>


    <div class="content-wrapper">
        <div class="content-wrapper__inner">
            <article class="post-container post-container--single" itemscope itemtype="http://schema.org/BlogPosting">
  <header class="post-header">
    <div class="post-meta" style="font-size:.8em">
      <time datetime="2017-01-16 00:00:00 +0800" itemprop="datePublished" class="post-meta__date date">2017-01-16</time> &#8226; <span class="post-meta__tags tags">Android,Multitouch</span> &#8226; View <span id="busuanzi_value_page_pv"></span> times.
</span>
    </div>
    <h1 class="post-title">安卓自定义View进阶-多点触控详解</h1>
  </header>

  <section class="post">
    <p>Android 多点触控详解，在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案，本次带大家了解 Android 多点触控相关的一些知识。</p>

<p><strong>多点触控</strong> ( <strong>Multitouch</strong>，也称 <strong>Multi-touch</strong> )，即同时接受屏幕上多个点的人机交互操作，多点触控是从 Android 2.0 开始引入的功能，在 Android 2.2 时对这一部分进行了重新设计。</p>

<p>在本文开始之前，先回顾一下 <a href="http://www.gcssloop.com/customview/motionevent">MotionEvent详解</a> 中提到过的内容：</p>

<ul>
  <li>Android 将所有的事件都封装进了 <code class="highlighter-rouge">Motionvent</code> 中。</li>
  <li>我们可以通过复写 <code class="highlighter-rouge">onTouchEvent</code> 或者设置 <code class="highlighter-rouge">OnTouchListener</code> 来获取 View 的事件。</li>
  <li>多点触控获取事件类型请使用 <code class="highlighter-rouge">getActionMasked()</code> 。</li>
  <li>追踪事件流请使用 <code class="highlighter-rouge">PointId</code>。</li>
</ul>

<p><strong>多点触控相关的事件：</strong></p>

<table>
  <thead>
    <tr>
      <th>事件</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ACTION_DOWN</td>
      <td><strong>第一个</strong> 手指 <strong>初次接触到屏幕</strong> 时触发。</td>
    </tr>
    <tr>
      <td>ACTION_MOVE</td>
      <td>手指 <strong>在屏幕上滑动</strong> 时触发，会多次触发。</td>
    </tr>
    <tr>
      <td>ACTION_UP</td>
      <td><strong>最后一个</strong> 手指 <strong>离开屏幕</strong> 时触发。</td>
    </tr>
    <tr>
      <td><strong>ACTION_POINTER_DOWN</strong></td>
      <td>有非主要的手指按下(<strong>即按下之前已经有手指在屏幕上</strong>)。</td>
    </tr>
    <tr>
      <td><strong>ACTION_POINTER_UP</strong></td>
      <td>有非主要的手指抬起(<strong>即抬起之后仍然有手指在屏幕上</strong>)。</td>
    </tr>
    <tr>
      <td>以下事件类型不推荐使用</td>
      <td>－－－以下事件在 2.2 版本以上被标记为废弃－－－</td>
    </tr>
    <tr>
      <td><del>ACTION_POINTER_1_DOWN</del></td>
      <td>第 2 个手指按下，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td><del>ACTION_POINTER_2_DOWN</del></td>
      <td>第 3 个手指按下，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td><del>ACTION_POINTER_3_DOWN</del></td>
      <td>第 4 个手指按下，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td><del>ACTION_POINTER_1_UP</del></td>
      <td>第 2 个手指抬起，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td><del>ACTION_POINTER_2_UP</del></td>
      <td>第 3 个手指抬起，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td><del>ACTION_POINTER_3_UP</del></td>
      <td>第 4 个手指抬起，已废弃，不推荐使用。</td>
    </tr>
  </tbody>
</table>

<p><strong>多点触控相关的方法：</strong></p>

<table>
  <thead>
    <tr>
      <th>方法</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>getActionMasked()</td>
      <td>与 <code class="highlighter-rouge">getAction()</code> 类似，<strong>多点触控需要使用这个方法获取事件类型</strong>。</td>
    </tr>
    <tr>
      <td>getActionIndex()</td>
      <td>获取该事件是哪个指针(手指)产生的。</td>
    </tr>
    <tr>
      <td>getPointerCount()</td>
      <td>获取在屏幕上手指的个数。</td>
    </tr>
    <tr>
      <td>getPointerId(int pointerIndex)</td>
      <td>获取一个指针(手指)的唯一标识符ID，在手指按下和抬起之间ID始终不变。</td>
    </tr>
    <tr>
      <td>findPointerIndex(int pointerId)</td>
      <td>通过PointerId获取到当前状态下PointIndex，之后通过PointIndex获取其他内容。</td>
    </tr>
    <tr>
      <td>getX(int pointerIndex)</td>
      <td>获取某一个指针(手指)的X坐标</td>
    </tr>
    <tr>
      <td>getY(int pointerIndex)</td>
      <td>获取某一个指针(手指)的Y坐标</td>
    </tr>
  </tbody>
</table>

<p>回顾完毕，开始正文。</p>

<h2 id="一多点触控相关问题">一、多点触控相关问题</h2>

<p>在引入多点触控之前，事件的类型很少，基本事件类型只有按下(down)、移动(move) 和 抬起(up)，即便加上那些特殊的事件类型也只有几种而已，所以我们可以用几个常量来标记这些事件，在使用的时候使用 <code class="highlighter-rouge">getAction()</code> 方法来获取具体的事件，之后和这些常量进行对比就行了。</p>

<p>在 Android 2.0 版本的时候，开始引入多点触控技术，由于技术上并不成熟，硬件和驱动也跟不上，多数设备只能支持追踪两三个点而已，因此在设计 API 上采取了一种简单粗暴的方案，添加了几个常量用于多点触控的事件类型的判断。</p>

<table>
  <thead>
    <tr>
      <th>事件</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ACTION_POINTER_1_DOWN</td>
      <td>第 2 个手指按下，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td>ACTION_POINTER_2_DOWN</td>
      <td>第 3 个手指按下，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td>ACTION_POINTER_3_DOWN</td>
      <td>第 4 个手指按下，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td>ACTION_POINTER_1_UP</td>
      <td>第 2 个手指抬起，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td>ACTION_POINTER_2_UP</td>
      <td>第 3 个手指抬起，已废弃，不推荐使用。</td>
    </tr>
    <tr>
      <td>ACTION_POINTER_3_UP</td>
      <td>第 4 个手指抬起，已废弃，不推荐使用。</td>
    </tr>
  </tbody>
</table>

<p>这些事件类型是用来判断非主要手指(第一个按下的称为主要手指)的按下和抬起，使用起来大概是这样子：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="k">switch</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getAction</span><span class="o">())</span> <span class="o">{</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span><span class="o">:</span>           <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_UP</span><span class="o">:</span>             <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_MOVE</span><span class="o">:</span>           <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_1_DOWN</span><span class="o">:</span> <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_2_DOWN</span><span class="o">:</span> <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_3_DOWN</span><span class="o">:</span> <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_1_UP</span><span class="o">:</span>   <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_2_UP</span><span class="o">:</span>   <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_3_UP</span><span class="o">:</span>   <span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
</div>

<p>看到这里可能会产生以下的一些疑问？</p>

<h3 id="1为什么没有-action_pointer_x_move-">1.为什么没有 ACTION_POINTER_X_MOVE ?</h3>

<p>在多指触控中所有的移动事件都是使用 <code class="highlighter-rouge">ACTION_MOVE</code>， 并没有追踪某一个手指的 move 事件类型，个人猜测主要是因为：<strong>很难无歧义的实现单独追踪每一个手指。</strong></p>

<p>要理解这个，首先要明白设备是如何识别多点触控的，设备没有眼睛，不能像我们人一样看到有几个手指(或者触控笔)在屏幕上。<br />
目前大多数 Android 设备都是电容屏，它们感知触摸是利用手指(触控笔)与屏幕接触产生的微小电流变化，之后通过计算这些电流变化来得出具体的触摸位置，在多点触控中，当两个触摸点足够靠近时，设备实际上是无法分清这两个点的。因此当两个触摸点靠近(重合)后再分开，设备很可能就无法正确的追踪两个点了，所以也很难实现无歧义的追踪每一个点。</p>

<p>并且从软件上来说，事件的编号产生和复用也是一个大问题，例如下面的场景：</p>

<table>
  <thead>
    <tr>
      <th>事件</th>
      <th style="text-align: center">手指数量</th>
      <th>编号变化</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>一个手指按下(命名为A)</td>
      <td style="text-align: center">1</td>
      <td>A手指的编号为0，id为0</td>
    </tr>
    <tr>
      <td>一个手指按下(命名为B)</td>
      <td style="text-align: center">2</td>
      <td>B手指的编号为1，id为1</td>
    </tr>
    <tr>
      <td>A手指抬起</td>
      <td style="text-align: center">1</td>
      <td>B手指编号变更为0，id不变为1</td>
    </tr>
    <tr>
      <td>一个手指按下(命名为C)</td>
      <td style="text-align: center">2</td>
      <td>C手指编号为0，id为0，B手指编号为1，id为1</td>
    </tr>
  </tbody>
</table>

<p>注意观察上面编号和id的变化，有两个问题，<strong>1、B手指的编号变化了。2、A手指和C手指id是相同的(A手指抬起后，C手指按下替代了A手指)。</strong>所以这就引出了一个问题：如果存在 ACTION_POINTER_X_MOVE，那么X应该用什么标志呢？编号会变化，id虽然不会变化，但id会被复用，例如A手指抬起后C手指按下，C手指复用了A手指的id。所以不论使用哪一个都不能保证唯一性。</p>

<p>当然了，解决问题最好的方式就是把问题抛出去，既然从硬件和软件上都不能保证唯一性和不变性，就不做区分了，因此所有的 move 事件都是 <code class="highlighter-rouge">ACTION_MOVE</code>, 具体是哪个手指产生的 move 用户可以结合其他事件(按下和抬起)来综合判断。</p>

<h3 id="2超过4个手指怎么办">2.超过4个手指怎么办？</h3>

<p><strong>2.0 兼容版</strong>，在2.2 之前的设计中，其提供的常量最多能判断四个手指的抬起和落下，当超过四个手指时怎么办呢？</p>

<p>由于在 2.2 版本之前，由于没有 <code class="highlighter-rouge">getActionMasked</code> 方法，我们可以自己自己手动进行计算，例如下面这样 ：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">String</span> <span class="n">TAG</span> <span class="o">=</span> <span class="s">"Gcs"</span><span class="o">;</span>

<span class="kt">int</span> <span class="n">action</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="na">getAction</span><span class="o">()</span> <span class="o">&amp;</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_MASK</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getAction</span><span class="o">()</span> <span class="o">&amp;</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_INDEX_MASK</span><span class="o">)</span>
        <span class="o">&gt;&gt;</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_INDEX_SHIFT</span><span class="o">;</span>

<span class="k">switch</span> <span class="o">(</span><span class="n">action</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span><span class="o">:</span>
        <span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span><span class="s">"第1个手指按下"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_UP</span><span class="o">:</span>
        <span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span><span class="s">"最后1个手指抬起"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_1_DOWN</span><span class="o">:</span> <span class="c1">// 此时相当于 ACTION_POINTER_DOWN</span>
        <span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span><span class="s">"第"</span><span class="o">+(</span><span class="n">index</span><span class="o">+</span><span class="mi">1</span><span class="o">)+</span><span class="s">"个手指按下"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_1_UP</span><span class="o">:</span>   <span class="c1">// 此时相当于 ACTION_POINTER_UP</span>
        <span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span><span class="s">"第"</span><span class="o">+(</span><span class="n">index</span><span class="o">+</span><span class="mi">1</span><span class="o">)+</span><span class="s">"个手指抬起"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
</div>

<p>在上面的例子中有几点比较关键：</p>

<h4 id="21action-与-index-的获得">2.1、action 与 Index 的获得</h4>

<p>我们在 <a href="http://www.gcssloop.com/customview/motionevent">MotionEvent详解</a> 中了解过，Android中的事件一般用最后8位来表示事件类型，再往前8位来表示Index。</p>

<p>例如多指触控的按下事件，其事件类型是 0x000000<strong>05</strong>， 其Index标志位是 0x0000<strong>00</strong>05，随着更多的手指按下，其中变化的部分是 Index 标志位，最后两位是始终不变的，所以我们只要能将这两个分离开就行了。</p>

<p><strong>取得事件类型(action)</strong></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 获取事件类型</span>
<span class="kt">int</span> <span class="n">action</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="na">getAction</span><span class="o">()</span> <span class="o">&amp;</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_MASK</span><span class="o">;</span>
</code></pre>
</div>

<p>这个非常简单，ACTION_MASK=0x000000ff， 与 getAction() 进行按位与操作后保留最后8位内容(十六进制每一个字符转化为二进制是4位)。</p>

<p>例如：<br />
0x000001<strong>05</strong> &amp; 0x000000ff = 0x000000<strong>05</strong></p>

<p><strong>取得事件索引(index)</strong></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 获取index编号</span>
<span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getAction</span><span class="o">()</span> <span class="o">&amp;</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_INDEX_MASK</span><span class="o">)</span>
        <span class="o">&gt;&gt;</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_INDEX_SHIFT</span><span class="o">;</span>
</code></pre>
</div>

<p>ACTION_POINTER_INDEX_MASK = 0x0000ff00<br />
ACTION_POINTER_INDEX_SHIFT = 8<br />
首先让 getAction() 与 ACTION_POINTER_INDEX_MASK 按位与之后，只保留 Index 那8位，之后再右移8位，最终就拿到了 Index 的真实数值。</p>

<p>例如： <br />
0x0000<strong>01</strong>05 &amp; 0x0000ff00 = 0x0000<strong>01</strong>00  <br />
0x0000<strong>01</strong>00 » 8 = 0x000000<strong>01</strong></p>

<h4 id="22用-action_pointer_1_down-代替-action_pointer_down">2.2、用 ACTION_POINTER_1_DOWN 代替 ACTION_POINTER_DOWN</h4>

<p>这是因为在 2.0 版本的时候还没有 ACTION_POINTER_DOWN 的这个常量，但是它们两个点数值是相同的，都是 0x00000005，这个你可以查看官方文档或者源码，甚至你直接写 <code class="highlighter-rouge">case 0x00000005</code> 也行，抬起也是同理。</p>

<h4 id="23只考虑兼容-22-以上的版本">2.3、只考虑兼容 2.2 以上的版本</h4>

<p>当然了，如果你不需要兼容 2.0 版本，只需要兼容到 2.2 以上的话就很简单了，像下面这样：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">String</span> <span class="n">TAG</span> <span class="o">=</span> <span class="s">"Gcs"</span><span class="o">;</span>

<span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="na">getActionIndex</span><span class="o">();</span>

<span class="k">switch</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getActionMasked</span><span class="o">())</span> <span class="o">{</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span><span class="o">:</span>
        <span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span><span class="s">"第1个手指按下"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_UP</span><span class="o">:</span>
        <span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span><span class="s">"最后1个手指抬起"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_DOWN</span><span class="o">:</span>
        <span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span><span class="s">"第"</span><span class="o">+(</span><span class="n">index</span><span class="o">+</span><span class="mi">1</span><span class="o">)+</span><span class="s">"个手指按下"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_UP</span><span class="o">:</span>
        <span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span><span class="s">"第"</span><span class="o">+(</span><span class="n">index</span><span class="o">+</span><span class="mi">1</span><span class="o">)+</span><span class="s">"个手指抬起"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
</div>

<h3 id="3-index-和-pointid-的变化规则">3. index 和 pointId 的变化规则</h3>

<p>在 2.2 版本以上，我们可以通过 getActionIndex() 轻松获取到事件的索引(Index)，但是这个事件索引的变化还是有点意思的，Index 变化有以下几个特点：</p>

<p>1、从 0 开始，自动增长。<br />
2、如果之前落下的手指抬起，后面手指的 Index 会随之减小。<br />
3、Index 变化趋向于第一次落下的数值(落下手指时，前面有空缺会优先填补空缺)。<br />
4、对 move 事件无效。</p>

<p>下面我们逐条解释一下具体含义。</p>

<h4 id="31从-0-开始自动增长">3.1、从 0 开始，自动增长。</h4>

<p>这一条非常简单，也很容易理解，而且在 <a href="http://www.gcssloop.com/customview/motionevent">MotionEvent详解</a> 中讲解 getAction() 与 getActionMasked() 也简单说过。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">手指按下</th>
      <th style="text-align: left">触发事件(数值)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">第1个手指按下</td>
      <td style="text-align: left">ACTION_DOWN                  (0x0000<strong>00</strong>00)</td>
    </tr>
    <tr>
      <td style="text-align: center">第2个手指按下</td>
      <td style="text-align: left">ACTION_POINTER_DOWN (0x0000<strong>01</strong>05)</td>
    </tr>
    <tr>
      <td style="text-align: center">第3个手指按下</td>
      <td style="text-align: left">ACTION_POINTER_DOWN (0x0000<strong>02</strong>05)</td>
    </tr>
    <tr>
      <td style="text-align: center">第4个手指按下</td>
      <td style="text-align: left">ACTION_POINTER_DOWN (0x0000<strong>03</strong>05)</td>
    </tr>
  </tbody>
</table>

<p>注意加粗的位置，数值随着手指按下而不断变大。</p>

<h4 id="32如果之前落下的手指抬起后面手指的-index-会随之减小">3.2、如果之前落下的手指抬起，后面手指的 Index 会随之减小。</h4>

<p>这个也比较容易理解，像下面这样：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">手指按下</th>
      <th style="text-align: left">触发事件(数值)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">第1个手指按下</td>
      <td style="text-align: left">ACTION_DOWN                  (0x0000<strong>00</strong>00)</td>
    </tr>
    <tr>
      <td style="text-align: center">第2个手指按下</td>
      <td style="text-align: left">ACTION_POINTER_DOWN (0x0000<strong>01</strong>05)</td>
    </tr>
    <tr>
      <td style="text-align: center">第3个手指按下</td>
      <td style="text-align: left">ACTION_POINTER_DOWN (0x0000<strong>02</strong>05)</td>
    </tr>
    <tr>
      <td style="text-align: center">第2个手指抬起</td>
      <td style="text-align: left">ACTION_POINTER_UP        (0x0000<strong>01</strong>06)</td>
    </tr>
    <tr>
      <td style="text-align: center">第3个手指抬起</td>
      <td style="text-align: left">ACTION_POINTER_UP        (0x0000<strong>01</strong>06)</td>
    </tr>
  </tbody>
</table>

<p>注意最后两次触发的事件，它的 Index 都是 1，这样也比较容易解释，当原本的第 2 个手指抬起后，屏幕上就只剩下两个手指了，之前的第 3 个手指就变成了第 2 个，于是抬起时触发事件的 Index 为 1，即之前落下的手指抬起，后面手指的 Index 会随之减小。</p>

<h4 id="33index-变化趋向于第一次落下的数值落下手指时前面有空缺会优先填补空缺">3.3、Index 变化趋向于第一次落下的数值(落下手指时，前面有空缺会优先填补空缺)。</h4>

<p>这个就有点神奇了，通过上一条规则，我们知道，某一个手指的 Index 可能会随着其他手指的抬起而变小，这次我们用 4 个手指测试一下 Index 的变化趋势。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">手指按下</th>
      <th style="text-align: left">触发事件(数值)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">第1个手指按下</td>
      <td style="text-align: left">ACTION_DOWN                  (0x0000<strong>00</strong>00)</td>
    </tr>
    <tr>
      <td style="text-align: center">第2个手指按下</td>
      <td style="text-align: left">ACTION_POINTER_DOWN (0x0000<strong>01</strong>05)</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>第3个手指按下</strong></td>
      <td style="text-align: left">ACTION_POINTER_DOWN (0x0000<strong>02</strong>05)</td>
    </tr>
    <tr>
      <td style="text-align: center">第2个手指抬起</td>
      <td style="text-align: left">ACTION_POINTER_UP        (0x0000<strong>01</strong>06)</td>
    </tr>
    <tr>
      <td style="text-align: center"><del>第3个手指抬起</del></td>
      <td style="text-align: left"><del>ACTION_POINTER_UP</del>        <del>(0x0000<strong>01</strong>06)</del></td>
    </tr>
    <tr>
      <td style="text-align: center">第4个手指按下</td>
      <td style="text-align: left">ACTION_POINTER_DOWN (0x0000<strong>01</strong>05)</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>第3个手指抬起</strong></td>
      <td style="text-align: left">ACTION_POINTER_UP        (0x0000<strong>02</strong>06)</td>
    </tr>
  </tbody>
</table>

<p>这个要和上一个对比这看，<strong>重点观察第 3 个手指所触发事件区别</strong>，在上一个示例中，随着第 2 个手指的抬起，第 3 个手指变化为第 2(01) 个，所以抬起时触发的是第 2 根手指的抬起事件(删除线部分)。</p>

<p>但是，如果第 2 个手指抬起后，落在屏幕上另外一个手指会怎样？经过测试，发现另外<strong>落下的手指会替代之前第 2 个手指的位置，系统判定为 2(01)，而不是顺延下去变成 3(02)，并且原本第3个手指的index变为原来数值(02)</strong>，但是如果继续落下其他的手指，数值则会顺延。</p>

<p><strong>即手指抬起时的 Index 会趋向于和按下时相同，虽然在手指数量不足时，Index 会变小，但是当手指变多时，Index 会趋向于保持和按下时一样。</strong></p>

<blockquote>
  <p>PS：由于程序是从0开始计数的，所以 0 就是 1， 1 就是 2 …</p>
</blockquote>

<h4 id="34对-move-事件无效">3.4、对 move 事件无效。</h4>

<p>这个也比较容易理解，我们所取得的 Index 属性实际上是从事件上分离下来的，但是 move 事件始终为  0x0000<strong>00</strong>02，也就是说，在 move 时不论你移动哪个手指，使用 <code class="highlighter-rouge">getActionIndex()</code>  获取到的始终是数值 0。</p>

<p>既然 move 事件无法用事件索引(Index)区别，那么该如何区分 move 是那个手指发出的呢？这就要用到 pointId 了，<strong>pointId 和 index 最大的区别就是 pointId 是不变的，始终为第一次落下时生成的数值，不会受到其他手指抬起和落下的影响。</strong></p>

<h4 id="35pointid-与-index-的异同">3.5、pointId 与 index 的异同。</h4>

<table>
  <thead>
    <tr>
      <th>相同点</th>
      <th>不同点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1. 从 0 开始，自动增长。<br />2. 落下手指时优先填补空缺(填补之前抬起手指的编号)。</td>
      <td>1. Index 会变化，pointId 始终不变。</td>
    </tr>
  </tbody>
</table>

<h3 id="4-move-相关事件">4. Move 相关事件</h3>

<h4 id="41-actionindex-与-pointerindex">4.1 actionIndex 与 pointerIndex</h4>

<p>在 move 中无法取得 actionIndex 的，我们需要使用 pointerIndex 来获取更多的信息，例如某个手指的坐标：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">getX</span><span class="o">(</span><span class="kt">int</span> <span class="n">pointerIndex</span><span class="o">)</span>
<span class="n">getY</span><span class="o">(</span><span class="kt">int</span> <span class="n">pointerIndex</span><span class="o">)</span>
</code></pre>
</div>

<p><strong>但是这个 pointerIndex 又是什么呢？和 actionIndex 有区别么？</strong></p>

<p>实际上这个 pointerIndex 和 actionIndex 区别并不大，两者的数值是相同的，你可以认为 pointerIndex 是特地为 move 事件准备的 actionIndex。</p>

<h4 id="42-pointerindex-与-pointerid">4.2 pointerIndex 与 pointerId</h4>

<table>
  <thead>
    <tr>
      <th>类型</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>pointerIndex</td>
      <td>用于获取具体事件，可能会随着其他手指的抬起和落下而变化</td>
    </tr>
    <tr>
      <td>pointerId</td>
      <td>用于识别手指，手指按下时产生，手指抬起时回收，期间始终不变</td>
    </tr>
  </tbody>
</table>

<p>这两个数值使用以下两个方法相互转换。</p>

<table>
  <thead>
    <tr>
      <th>方法</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>getPointerId(int pointerIndex)</td>
      <td>获取一个指针(手指)的唯一标识符ID，在手指按下和抬起之间ID始终不变。</td>
    </tr>
    <tr>
      <td>findPointerIndex(int pointerId)</td>
      <td>通过 pointerId 获取到当前状态下 pointIndex，之后通过 pointIndex 获取其他内容。</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>通常情况下，pointerIndex 和 pointerId 是相同的，但也可能会因为某些手指的抬起而变得不同。</p>
</blockquote>

<h4 id="43-遍历多点触控">4.3 遍历多点触控</h4>

<p>先来一个简单的，遍历出多个手指的 move 事件：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">String</span> <span class="n">TAG</span> <span class="o">=</span> <span class="s">"Gcs"</span><span class="o">;</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getActionMasked</span><span class="o">())</span> <span class="o">{</span>
    <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_MOVE</span><span class="o">:</span>
        <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">event</span><span class="o">.</span><span class="na">getPointerCount</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
            <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"TAG"</span><span class="o">,</span> <span class="s">"pointerIndex="</span><span class="o">+</span><span class="n">i</span><span class="o">+</span><span class="s">", pointerId="</span><span class="o">+</span><span class="n">event</span><span class="o">.</span><span class="na">getPointerId</span><span class="o">(</span><span class="n">i</span><span class="o">));</span>
          	<span class="c1">// TODO</span>
        <span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>

<p>通过遍历 pointerCount 获取到所有的 pointerIndex，同时通过 pointerIndex 来获取 pointerId，可以通过不同手指抬起和按下后移动来观察 pointerIndex 和 pointerId 的变化。</p>

<h4 id="44-在多点触控中追踪单个手指">4.4 在多点触控中追踪单个手指</h4>

<p>要实现追踪单个手指还是有些麻烦的，需要同时使用上 actionIndex， pointerId 和 pointerIndex，例如，我们只追踪第2个手指，并画出其位置：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="cm">/**
 * 绘制出第二个手指第位置
 */</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MultiTouchTest</span> <span class="kd">extends</span> <span class="n">CustomView</span> <span class="o">{</span>
    <span class="n">String</span> <span class="n">TAG</span> <span class="o">=</span> <span class="s">"Gcs"</span><span class="o">;</span>

    <span class="c1">// 用于判断第2个手指是否存在</span>
    <span class="kt">boolean</span> <span class="n">haveSecondPoint</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>

    <span class="c1">// 记录第2个手指第位置</span>
    <span class="n">PointF</span> <span class="n">point</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PointF</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>

    <span class="kd">public</span> <span class="nf">MultiTouchTest</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nf">MultiTouchTest</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">AttributeSet</span> <span class="n">attrs</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">attrs</span><span class="o">);</span>

        <span class="n">mDeafultPaint</span><span class="o">.</span><span class="na">setAntiAlias</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
        <span class="n">mDeafultPaint</span><span class="o">.</span><span class="na">setTextAlign</span><span class="o">(</span><span class="n">Paint</span><span class="o">.</span><span class="na">Align</span><span class="o">.</span><span class="na">CENTER</span><span class="o">);</span>
        <span class="n">mDeafultPaint</span><span class="o">.</span><span class="na">setTextSize</span><span class="o">(</span><span class="mi">30</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onTouchEvent</span><span class="o">(</span><span class="n">MotionEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="na">getActionIndex</span><span class="o">();</span>

        <span class="k">switch</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getActionMasked</span><span class="o">())</span> <span class="o">{</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_DOWN</span><span class="o">:</span>
                <span class="c1">// 判断是否是第2个手指按下</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getPointerId</span><span class="o">(</span><span class="n">index</span><span class="o">)</span> <span class="o">==</span> <span class="mi">1</span><span class="o">){</span>
                    <span class="n">haveSecondPoint</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
                    <span class="n">point</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">());</span>
                <span class="o">}</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_UP</span><span class="o">:</span>
                <span class="c1">// 判断抬起的手指是否是第2个</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getPointerId</span><span class="o">(</span><span class="n">index</span><span class="o">)</span> <span class="o">==</span> <span class="mi">1</span><span class="o">){</span>
                    <span class="n">haveSecondPoint</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
                    <span class="n">point</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
                <span class="o">}</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_MOVE</span><span class="o">:</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">haveSecondPoint</span><span class="o">)</span> <span class="o">{</span>
                    <span class="c1">// 通过 pointerId 来获取 pointerIndex</span>
                    <span class="kt">int</span> <span class="n">pointerIndex</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="na">findPointerIndex</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
                    <span class="c1">// 通过 pointerIndex 来取出对应的坐标</span>
                    <span class="n">point</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(</span><span class="n">pointerIndex</span><span class="o">),</span> <span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">(</span><span class="n">pointerIndex</span><span class="o">));</span>
                <span class="o">}</span>
                <span class="k">break</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="n">invalidate</span><span class="o">();</span>   <span class="c1">// 刷新</span>

        <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDraw</span><span class="o">(</span><span class="n">Canvas</span> <span class="n">canvas</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">canvas</span><span class="o">.</span><span class="na">save</span><span class="o">();</span>
        <span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mViewWidth</span><span class="o">/</span><span class="mi">2</span><span class="o">,</span> <span class="n">mViewHeight</span><span class="o">/</span><span class="mi">2</span><span class="o">);</span>
        <span class="n">canvas</span><span class="o">.</span><span class="na">drawText</span><span class="o">(</span><span class="s">"追踪第2个按下手指的位置"</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>
        <span class="n">canvas</span><span class="o">.</span><span class="na">restore</span><span class="o">();</span>

        <span class="c1">// 如果屏幕上有第2个手指则绘制出来其位置</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">haveSecondPoint</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">canvas</span><span class="o">.</span><span class="na">drawCircle</span><span class="o">(</span><span class="n">point</span><span class="o">.</span><span class="na">x</span><span class="o">,</span> <span class="n">point</span><span class="o">.</span><span class="na">y</span><span class="o">,</span> <span class="mi">50</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>

<p>这段代码也非常短，其核心就是通过判断数值为 1 的 pointerId 是否存在，如果存在就在 move 的时候取出其坐标，并绘制出来。</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071428.jpg?gcssloop" alt="SecondPoint" /></p>

<blockquote>
  <p>虽然逻辑简单，但个人感觉写起来还是有些麻烦，如果有更简单的方案欢迎告诉我。</p>
</blockquote>

<h2 id="二如何使用多点触控">二、如何使用多点触控</h2>

<p>多点触控应用还是比较广泛的，至少目前大部分的图片查看都需要用到多点触控技术(用于拖动和缩放图片)。</p>

<p>但是在某些看似不需要多触控的地方也需要对多点触控进行判断，只要是多点触控可能引起错误的地方都应该加上多点触控的判断。例如使用到 move 事件的时候，由于 move 事件可能由多个手指同时触发，所以可能会出现同时被多个手指控制的情况，如果不适当的处理，这个 move 就可能由任何一个手指触发。</p>

<p>举一个简单的例子：</p>

<p>如果我们需要一个<strong>可以用单指拖动的图片</strong>。假如我们不进行多指触控的判断，像下面这样：</p>

<p><strong>没有针对多指触控处理版本：</strong></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="cm">/**
 * 一个可以拖图片动的 View
 */</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DragView1</span> <span class="kd">extends</span> <span class="n">CustomView</span> <span class="o">{</span>
    <span class="n">String</span> <span class="n">TAG</span> <span class="o">=</span> <span class="s">"Gcs"</span><span class="o">;</span>

    <span class="n">Bitmap</span> <span class="n">mBitmap</span><span class="o">;</span>         <span class="c1">// 图片</span>
    <span class="n">RectF</span> <span class="n">mBitmapRectF</span><span class="o">;</span>     <span class="c1">// 图片所在区域</span>
    <span class="n">Matrix</span> <span class="n">mBitmapMatrix</span><span class="o">;</span>   <span class="c1">// 控制图片的 matrix</span>

    <span class="kt">boolean</span> <span class="n">canDrag</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
    <span class="n">PointF</span> <span class="n">lastPoint</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PointF</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>

    <span class="kd">public</span> <span class="nf">DragView1</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nf">DragView1</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">AttributeSet</span> <span class="n">attrs</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">attrs</span><span class="o">);</span>

      	<span class="c1">// 调整图片大小</span>
        <span class="n">BitmapFactory</span><span class="o">.</span><span class="na">Options</span> <span class="n">options</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BitmapFactory</span><span class="o">.</span><span class="na">Options</span><span class="o">();</span>
        <span class="n">options</span><span class="o">.</span><span class="na">outWidth</span> <span class="o">=</span> <span class="mi">960</span><span class="o">/</span><span class="mi">2</span><span class="o">;</span>
        <span class="n">options</span><span class="o">.</span><span class="na">outHeight</span> <span class="o">=</span> <span class="mi">800</span><span class="o">/</span><span class="mi">2</span><span class="o">;</span>

        <span class="n">mBitmap</span> <span class="o">=</span> <span class="n">BitmapFactory</span><span class="o">.</span><span class="na">decodeResource</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">getResources</span><span class="o">(),</span> <span class="n">R</span><span class="o">.</span><span class="na">drawable</span><span class="o">.</span><span class="na">drag_test</span><span class="o">,</span> <span class="n">options</span><span class="o">);</span>
        <span class="n">mBitmapRectF</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="n">mBitmap</span><span class="o">.</span><span class="na">getWidth</span><span class="o">(),</span> <span class="n">mBitmap</span><span class="o">.</span><span class="na">getHeight</span><span class="o">());</span>
        <span class="n">mBitmapMatrix</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Matrix</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onTouchEvent</span><span class="o">(</span><span class="n">MotionEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">switch</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getActionMasked</span><span class="o">())</span> <span class="o">{</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span><span class="o">:</span>
                <span class="c1">// 判断按下位置是否包含在图片区域内</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">mBitmapRectF</span><span class="o">.</span><span class="na">contains</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(),</span> <span class="o">(</span><span class="kt">int</span><span class="o">)</span><span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">())){</span>
                    <span class="n">canDrag</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
                    <span class="n">lastPoint</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">());</span>
             <span class="o">}</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_UP</span><span class="o">:</span>
                <span class="n">canDrag</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_MOVE</span><span class="o">:</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">canDrag</span><span class="o">)</span> <span class="o">{</span>
                    <span class="c1">// 移动图片</span>
                    <span class="n">mBitmapMatrix</span><span class="o">.</span><span class="na">postTranslate</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">()</span> <span class="o">-</span> <span class="n">lastPoint</span><span class="o">.</span><span class="na">x</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">()</span> <span class="o">-</span> <span class="n">lastPoint</span><span class="o">.</span><span class="na">y</span><span class="o">);</span>
                    <span class="c1">// 更新上一次点位置</span>
                    <span class="n">lastPoint</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">());</span>

                    <span class="c1">// 更新图片区域</span>
                    <span class="n">mBitmapRectF</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">mBitmap</span><span class="o">.</span><span class="na">getWidth</span><span class="o">(),</span> <span class="n">mBitmap</span><span class="o">.</span><span class="na">getHeight</span><span class="o">());</span>
                    <span class="n">mBitmapMatrix</span><span class="o">.</span><span class="na">mapRect</span><span class="o">(</span><span class="n">mBitmapRectF</span><span class="o">);</span>

                    <span class="n">invalidate</span><span class="o">();</span>
                <span class="o">}</span>
                <span class="k">break</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDraw</span><span class="o">(</span><span class="n">Canvas</span> <span class="n">canvas</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">canvas</span><span class="o">.</span><span class="na">drawBitmap</span><span class="o">(</span><span class="n">mBitmap</span><span class="o">,</span> <span class="n">mBitmapMatrix</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>

<p>这个版本非常简单，当然了，如果正常使用(只使用一个手指)的话也不会出问题，但是当使用多个手指，且有抬起和按下的时候就可能出问题，下面用一个典型的场景演示一下：</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071430.gif?gcssloop" alt="dragview1" /></p>

<p>注意在第二个手指按下，第一个手指抬起时，此时原本的第二个手指会被识别为第一个，所以图片会直接跳动到第二个手指位置。</p>

<p>为了不出现这种情况，我们可以判断一下 pointId 并且只获取第一个手指的数据，这样就能避免这种情况发生了，如下。</p>

<p><strong>针对多指触控处理后版本：</strong></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="cm">/**
 * 一个可以拖图片动的 View
 */</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DragView</span> <span class="kd">extends</span> <span class="n">CustomView</span> <span class="o">{</span>
    <span class="n">String</span> <span class="n">TAG</span> <span class="o">=</span> <span class="s">"Gcs"</span><span class="o">;</span>

    <span class="n">Bitmap</span> <span class="n">mBitmap</span><span class="o">;</span>         <span class="c1">// 图片</span>
    <span class="n">RectF</span> <span class="n">mBitmapRectF</span><span class="o">;</span>     <span class="c1">// 图片所在区域</span>
    <span class="n">Matrix</span> <span class="n">mBitmapMatrix</span><span class="o">;</span>   <span class="c1">// 控制图片的 matrix</span>

    <span class="kt">boolean</span> <span class="n">canDrag</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
    <span class="n">PointF</span> <span class="n">lastPoint</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PointF</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>

    <span class="kd">public</span> <span class="nf">DragView</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nf">DragView</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">AttributeSet</span> <span class="n">attrs</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">attrs</span><span class="o">);</span>

        <span class="n">BitmapFactory</span><span class="o">.</span><span class="na">Options</span> <span class="n">options</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BitmapFactory</span><span class="o">.</span><span class="na">Options</span><span class="o">();</span>
        <span class="n">options</span><span class="o">.</span><span class="na">outWidth</span> <span class="o">=</span> <span class="mi">960</span><span class="o">/</span><span class="mi">2</span><span class="o">;</span>
        <span class="n">options</span><span class="o">.</span><span class="na">outHeight</span> <span class="o">=</span> <span class="mi">800</span><span class="o">/</span><span class="mi">2</span><span class="o">;</span>

        <span class="n">mBitmap</span> <span class="o">=</span> <span class="n">BitmapFactory</span><span class="o">.</span><span class="na">decodeResource</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">getResources</span><span class="o">(),</span> <span class="n">R</span><span class="o">.</span><span class="na">drawable</span><span class="o">.</span><span class="na">drag_test</span><span class="o">,</span> <span class="n">options</span><span class="o">);</span>
        <span class="n">mBitmapRectF</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="n">mBitmap</span><span class="o">.</span><span class="na">getWidth</span><span class="o">(),</span> <span class="n">mBitmap</span><span class="o">.</span><span class="na">getHeight</span><span class="o">());</span>
        <span class="n">mBitmapMatrix</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Matrix</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onTouchEvent</span><span class="o">(</span><span class="n">MotionEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">switch</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getActionMasked</span><span class="o">())</span> <span class="o">{</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span><span class="o">:</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_DOWN</span><span class="o">:</span>
                <span class="c1">// ▼ 判断是否是第一个手指 &amp;&amp; 是否包含在图片区域内</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getPointerId</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getActionIndex</span><span class="o">())</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">mBitmapRectF</span><span class="o">.</span><span class="na">contains</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(),</span> <span class="o">(</span><span class="kt">int</span><span class="o">)</span><span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">())){</span>
                    <span class="n">canDrag</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
                    <span class="n">lastPoint</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">());</span>
                <span class="o">}</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_UP</span><span class="o">:</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_UP</span><span class="o">:</span>
                <span class="c1">// ▼ 判断是否是第一个手指</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getPointerId</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getActionIndex</span><span class="o">())</span> <span class="o">==</span> <span class="mi">0</span><span class="o">){</span>
                    <span class="n">canDrag</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
                <span class="o">}</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_MOVE</span><span class="o">:</span>
                <span class="c1">// 如果存在第一个手指，且这个手指的落点在图片区域内</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">canDrag</span><span class="o">)</span> <span class="o">{</span>
                    <span class="c1">// ▼ 注意 getX 和 getY</span>
                    <span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="na">findPointerIndex</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
                    <span class="c1">// Log.i(TAG, "index="+index);</span>
                    <span class="n">mBitmapMatrix</span><span class="o">.</span><span class="na">postTranslate</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(</span><span class="n">index</span><span class="o">)-</span><span class="n">lastPoint</span><span class="o">.</span><span class="na">x</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">(</span><span class="n">index</span><span class="o">)-</span><span class="n">lastPoint</span><span class="o">.</span><span class="na">y</span><span class="o">);</span>
                    <span class="n">lastPoint</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(</span><span class="n">index</span><span class="o">),</span> <span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">(</span><span class="n">index</span><span class="o">));</span>

                    <span class="n">mBitmapRectF</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="n">mBitmap</span><span class="o">.</span><span class="na">getWidth</span><span class="o">(),</span> <span class="n">mBitmap</span><span class="o">.</span><span class="na">getHeight</span><span class="o">());</span>
                    <span class="n">mBitmapMatrix</span><span class="o">.</span><span class="na">mapRect</span><span class="o">(</span><span class="n">mBitmapRectF</span><span class="o">);</span>

                    <span class="n">invalidate</span><span class="o">();</span>
                <span class="o">}</span>
                <span class="k">break</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDraw</span><span class="o">(</span><span class="n">Canvas</span> <span class="n">canvas</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">canvas</span><span class="o">.</span><span class="na">drawBitmap</span><span class="o">(</span><span class="n">mBitmap</span><span class="o">,</span> <span class="n">mBitmapMatrix</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>

<p>可以看到，比起上一个版本，只添加了少量代码，就变得更加“智能”了，可以准确识别某一个手指，不会因为手指抬起而认错手指。</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071432.gif?gcssloop" alt="dragview2" /></p>

<p><strong>重点注意最后，第一个手指抬起之后，图片并没有跳跃到第二个手指的位置。</strong></p>

<p>上面的两个对比示例都精简到了极致，其核心依旧是正确的追踪某一个手指，建议大家自己写一遍体会一下。</p>

<hr />

<p>我感觉很多人看到这里依旧是不明所以的，一些简单的东西还好弄，但是复杂一些，如同时处理多个手指的数值就有些困难了，<strong>假如说你之前没有接触过多点触控的处理，此时让你实现用两个手指来缩放图片还是有些困难的。</strong></p>

<p>因为这不仅要追踪两个手指的位置，还要根据位置变化来计算缩放比例和缩放中心，单单这两个非常简单的数学问题就能难倒一大批人。</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071433.gif?gcssloop" alt="-510a02a1a3f8d16d" /></p>

<p>当然了，很多麻烦问题都有简单的解决方案，假如说我们真的要实现一个可以用两个或者多个手指缩放的控件，何必要自己算呢，可以尝试一下 Android 自带的解决方案：<strong>手势检测(GestureDetector、ScaleGestureDecetor)</strong>，不仅能自动帮你计算好缩放比例和缩放中心，而且还可以检测出 单击、长按、滑屏 等不同的手势，不过这就不是本篇的事情了，以后有时间会写一下有关手势检测的用法(继续挖坑)。</p>

<h2 id="三总结">三、总结</h2>

<p>前段时间因为各种事情比较忙，这篇文章也没时间去写，所以就一直拖到了现在，期间收到不少读者催更，实在是抱歉了。今后在会尽量保证稳定更新的，争取尽快把自定义View系列这一个大坑填完。 ˊ_&gt;ˋ</p>

<p>关于多点触控，个人认为还算一个比较重要的知识点。尤其是随着 Android 的发展，很多炫酷的交互操作可能会需要用户进行拖拽操作。在进行这类操作的时候进行一下手指的判断还是相当重要的。</p>

<p>本文中需要注意的几个知识点：</p>

<ul>
  <li>如何兼容 2.0 版本的多点触控(目前大部分都不需要兼容 2.0 了吧)。</li>
  <li>actionIndex、pointIndex 与 pointId 的区别和用法。</li>
  <li>如何在多点触控中正确的追踪一个手指。</li>
</ul>

<h2 id="about-me">About Me</h2>

<h3 id="作者微博-gcssloop">作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a></h3>

<p><a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/gcs_banner.jpg?gcssloop" width="300" style="display:inline;" /></a></p>

<h2 id="参考资料">参考资料</h2>

<p><a href="https://developer.android.com/reference/android/view/MotionEvent.html">MotionEvent </a></p>


    <hr>
  </section>
</article>

<!--广告-->

<!--
<div>
<a href="https://m.aliyun.com/act/team1111/?spm=5176.11533457.1089570.5.424777e3AF8WOJ&userCode=hn5smxtw#/" target="_blank"><img src="/assets/aliyun/1111-980-120.jpg" width="100%"></a>
</div>
-->
<!--捐赠晶石-->
<section class="contribute">
    <script type="text/javascript"> 
      function toggle() {
        var con = document.getElementById("contribute");
        if (con.style.display == "none") {
          con.style.display = "block";
        } else {
          con.style.display = "none";
        }
      }
    </script> 
    <blockquote style="background-color:#F5F5F5; padding: 10px 20px 20px 10px; margin:0px" >
      <h4> 如果你觉得我的文章对你有帮助的话，欢迎赞助一些服务器费用! </h4>
      <p></p>
      <a id=“btn-toggle-contribute” class="btn-contribute" onclick="toggle()" >¥ 点击赞助</a>
      <br>
      <div id="contribute" style="display:none;">
        <p align="center" >
        <img src="/assets/images/wechat.png" alt="微信">
        <img src="/assets/images/alipay.png" alt="支付宝">
        </p>
        <p align="left" >
          <b>感谢所有支持我的魔法师，所有支持过我的魔法师都可以通过微信(GcsSloop)联系我，获赠我的付费专栏！</b>
          <!--
          <a href="/contribute">点击这里查看捐赠者名单。</a>
          -->
        </p>
      </div>
    </blockquote>
</section>
<div>
  <h2>欢迎关注我的微信公众号</h2>
  <img src="/assets/images/banner.jpg" width="100%">
</div>

<!--阅读更多-->
<section class="read-more">
  
  
  <div class="read-more-item">
    <span class="read-more-item-dim">最近的文章</span>
    <h2 class="post-list__post-title post-title"><a href="/customview/gestruedector" title="link to 安卓自定义View进阶-手势检测(GestureDetector)">安卓自定义View进阶-手势检测(GestureDetector)</a></h2>
    <p class="excerpt">Android 手势检测，主要是 GestureDetector 相关内容的用法和注意事项，本文依旧属于事件处理这一体系，部分内容会涉及到之前文章提及过的知识点，如果你没看过之前的文章，可以到 ...&hellip;</p>
    <div class="post-list__meta">
      <time datetime="2017-07-09 00:00:00 +0800" class="post-list__meta--date date">2017-07-09</time> &#8226; <span class="post-list__meta--tags tags">CustomView</span>
      <br/><br/>
      <a style="float:none; margin:0 auto;" class="btn-border-small" href=/customview/gestruedector>继续阅读</a></div>
   </div>
   
   
   
   
   <div class="read-more-item">
       <span class="read-more-item-dim">更早的文章</span>
       <h2 class="post-list__post-title post-title"><a href="/markdown/markdown-editor" title="link to Markdown实用技巧－编辑器(Typora)">Markdown实用技巧－编辑器(Typora)</a></h2>
       <p class="excerpt">本次的安利对象是一个 Markdown 编辑器，是会长1见过的最简单，最优雅的编辑器，先来看一下它的界面吧：它的界面非常简单，有多种主题可选，更重要的是它的预览界面和编辑界面是一体的，而不像其他...&hellip;</p>
       <div class="post-list__meta">
          <time datetime="2016-12-07 00:00:00 +0800" class="post-list__meta--date date">2016-12-07</time> &#8226; <span class="post-list__meta--tags tags">Markdown</span>
          <br/><br/>
          <a style="float:none; margin:0 auto;" class="btn-border-small" href=/markdown/markdown-editor>继续阅读</a>
       </div>
   </div>
   
</section>

<!--网易云跟帖-->
<!--
<div id="cloud-tie-wrapper" class="cloud-tie-wrapper"></div>
<script src="https://img1.cache.netease.com/f2e/tie/yun/sdk/loader.js"></script>
<script>
var cloudTieConfig = {
  url: document.location.href, 
  sourceId: "",
  productKey: "a85dba2840134721a7b69a15b2e0f217",
  target: "cloud-tie-wrapper"
};
var yunManualLoad = true;
Tie.loader("aHR0cHM6Ly9hcGkuZ2VudGllLjE2My5jb20vcGMvbGl2ZXNjcmlwdC5odG1s", true);
</script>
-->

<style type="text/css">
.isso-comment > div.avatar {
    border: 0px;
    box-shadow: none;
    display: block;
    float: left;
    width: 7%;
    margin: 3px 15px 0 0;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input {
    border-radius: 6px;
    padding: 6px;
    padding-left: 16px;
    padding-right: 16px;
    border: 1px solid #CCC;
    background-color: #D58D44;
    cursor: pointer;
    outline: 0;
    color: #fff;
    size: 10;
    line-height: 1.4em;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:hover {
    background-color: #272822;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:active {
    background-color: #986530;
}
</style>

<section id="isso-thread"></section>

<script data-isso="//47.52.58.34:1234/"
        data-isso-css="true"
        data-isso-lang="zh"
        data-isso-reply-to-self="false"
        data-isso-require-author="false"
        data-isso-require-email="false"
        data-isso-max-comments-top="10"
        data-isso-max-comments-nested="5"
        data-isso-reveal-on-click="5"
        data-isso-avatar="true"
        data-isso-avatar-bg="#f0f0f0"
        data-isso-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..."
        data-isso-vote="true"
        data-vote-levels=""
        src="//47.52.58.34:1234/js/embed.min.js">
        </script>

<!--
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
-->
<!-- OneV's Den -->
<!--
<ins class="adsbygoogle"
     style="display:block"
     data-ad-client="ca-pub-3324997515191619"
     data-ad-slot="9170309685"
     data-ad-format="auto"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
-->

            <section class="footer">
    <footer>
    	<span class="footer__copyright">本站点采用<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh" target="_blank">知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议</a></span>
        <span class="footer__copyright">本站由 <a href="http://www.GcsSloop.com">@GcsSloop</a> 创建，采用 <a href="https://github.com/GcsSloop/Gcs-Vno-Jekyll" target="_blank">Gcs-Vno-Jekyll</a> 作为主题。<span id="busuanzi_container_site_pv"> 总访问量 <span id="busuanzi_value_site_pv"></span> 次</span> - &copy; 2019</span>
        <span class="footer__sitemap, footer__copyright"><a href="http://www.gcssloop.com/sitemap.xml" target="_blank">Site Map</a>
        <a href="http://www.gcssloop.com/vip" target="_blank">vip</a></span>
    </footer>
</section>

        </div>
    </div>
    
    <script type="text/javascript" src="//code.jquery.com/jquery-1.11.3.min.js"></script>

<script type="text/javascript" src="/js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

<script type="text/javascript" src="/js/main.js"></script>

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-82493667-1', 'auto');
  ga('send', 'pageview');

</script>

    
  </body>

</html>
